﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace lhhp.net.Utility
{
    public class DynamicClassCreator
    {
        AppDomain myDomain;
        AssemblyName myassemblyName;
        AssemblyBuilder myassemblyBuilder;
        ModuleBuilder mymoduleBuilder;
        TypeBuilder myTypeBuilder;

        FieldBuilder myFiledBuilder;
        PropertyBuilder myPropertyBuilder;
        MethodBuilder myGetMethodBuilder;
        MethodBuilder mySetMethodBuilder;
        ILGenerator myILGenerator;
        Type myType;
        List<CustomPropertyInfo> propertyInfoArray;

        public DynamicClassCreator(string className)
        {
            myDomain = Thread.GetDomain();
            myassemblyName = new AssemblyName("MyDynamicAssembly");
            myassemblyBuilder = myDomain.DefineDynamicAssembly(myassemblyName, AssemblyBuilderAccess.RunAndCollect);
            mymoduleBuilder = myassemblyBuilder.DefineDynamicModule("MyDynamicModule");
            myTypeBuilder = mymoduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class);
            propertyInfoArray = new List<CustomPropertyInfo>();
        }

        public void CreateType()
        {
            myType = myTypeBuilder.CreateType();
        }

        public void AddPropertyDefinition(Type propertyType, string propertyName)
        {
            CustomPropertyInfo propertyInfo = propertyInfoArray.SingleOrDefault(p => p.PropertyName.Equals(propertyName));
            if (propertyInfo == null)
            {
                propertyInfo = new CustomPropertyInfo(propertyType, propertyName);
                propertyInfoArray.Add(propertyInfo);
                //定义属性对应的私有字段
                myFiledBuilder = myTypeBuilder.DefineField(propertyInfo.FieldName, propertyInfo.Type, FieldAttributes.Private | FieldAttributes.HasDefault);
                //定义属性
                myPropertyBuilder = myTypeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);

                // 属性Set和Get方法要一个专门的属性。这里设置为Public。
                MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
                //设置get访问器
                myGetMethodBuilder = myTypeBuilder.DefineMethod(propertyInfo.GetPropertyMethodName, getSetAttr, propertyInfo.Type, Type.EmptyTypes);
                myILGenerator = myGetMethodBuilder.GetILGenerator();
                myILGenerator.Emit(OpCodes.Ldarg_0);
                myILGenerator.Emit(OpCodes.Ldfld, myFiledBuilder);
                myILGenerator.Emit(OpCodes.Ret);
                myPropertyBuilder.SetGetMethod(myGetMethodBuilder);

                //设置set访问器
                mySetMethodBuilder = myTypeBuilder.DefineMethod(propertyInfo.SetPropertyMethodName, getSetAttr, null, new Type[] { propertyInfo.Type });
                myILGenerator = mySetMethodBuilder.GetILGenerator();
                myILGenerator.Emit(OpCodes.Ldarg_0);
                myILGenerator.Emit(OpCodes.Ldarg_1);
                myILGenerator.Emit(OpCodes.Stfld, myFiledBuilder);
                myILGenerator.Emit(OpCodes.Ret);
                myPropertyBuilder.SetSetMethod(mySetMethodBuilder);
            }
            else
            {
                throw new Exception("已经定义了该名称的属性");
            }
        }

        public void SetPropertyValue(object instance, string propertyName, object propertyValue)
        {
            if (myType == null)
            {
                throw new Exception("还没创建类型");
            }
            if (instance == null)
            {
                throw new Exception("请指定实例");
            }
            var pinfo = propertyInfoArray.SingleOrDefault(p => p.PropertyName.Equals(propertyName));
            if (pinfo == null)
            {
                throw new Exception(string.Format("不存在名叫{0}的属性", propertyName));
            }
            myType.InvokeMember(propertyName, BindingFlags.SetProperty, null, instance, new object[] { Convert.ChangeType(propertyValue, pinfo.Type) });
        }

        public dynamic CreateInstance()
        {
            dynamic d = Activator.CreateInstance(myType);
            return d;
        }
    }
}
